using System;
using System.Globalization;
using System.Text;
using Microsoft.Build.Framework;
using AntEater.ComponentModel.Building;

namespace AntEater.Runtime.Services.Building
{
    /// <summary>
    /// Default implementation of building logger for MSBuild.
    /// </summary>
    public class MSBuildBuildingLogger : BuildingLoggerBase, ILogger
    {
        private int indent;

        private string _parameters;

        /// <summary>
        /// Gets or sets the user-defined parameters of the logger.
        /// </summary>
        /// <value></value>
        /// <returns>The logger parameters.</returns>
        public string Parameters {
            get { return _parameters; }
            set { _parameters = value; }
        }

        /// <summary>
        /// Gets or sets the level of detail to show in the event log.
        /// </summary>
        /// <value></value>
        /// <returns>One of the enumeration values. The default is <see cref="F:Microsoft.Build.Framework.LoggerVerbosity.Normal"></see>.</returns>
        public LoggerVerbosity Verbosity {
            get { return (LoggerVerbosity)base.Context.Verbosity; }
            set { base.Context.Verbosity = (BuildingLoggerVerbosity)value; }
        }

        /// <summary>
        /// Subscribes loggers to specific events. This method is called when the logger is registered with the build engine, before any events are raised.
        /// </summary>
        /// <param name="eventSource">The events available to loggers.</param>
        public void Initialize(IEventSource eventSource) {

            if (eventSource == null) { throw new ArgumentNullException("eventSource"); }

            // Register genral events: ever logged
            eventSource.ErrorRaised += new BuildErrorEventHandler(BuildErrorRaised);
            eventSource.WarningRaised += new BuildWarningEventHandler(BuildWarningRaised);

            // Regsiter message events: logged by message importance
            eventSource.MessageRaised += new BuildMessageEventHandler(BuildMessageRaised);

            // Register Build events: logged with minimal verbosity of quiet
            eventSource.BuildStarted += new BuildStartedEventHandler(BuildBuildStarted);
            eventSource.BuildFinished += new BuildFinishedEventHandler(BuildBuildFinished);

            // Register Project events: logged with minimal verbosity of minimal
            eventSource.ProjectStarted += new ProjectStartedEventHandler(BuildProjectStarted);
            eventSource.ProjectFinished += new ProjectFinishedEventHandler(BuildProjectFinished);

            // Register Target events: logged with minimal verbosity of normal
            eventSource.TargetFinished += new TargetFinishedEventHandler(BuildTargetFinished);
            eventSource.TargetStarted += new TargetStartedEventHandler(BuildTargetStarted);

            // Register Task events: logged with minimal verbosity of detailed
            eventSource.TaskFinished += new TaskFinishedEventHandler(BuildTaskFinished);
            eventSource.TaskStarted += new TaskStartedEventHandler(BuildTaskStarted);

            // Register other events: logged with minimal verbosity of diagnostic
            eventSource.StatusEventRaised += new BuildStatusEventHandler(BuildStatusEventRaised); 
            eventSource.AnyEventRaised += new AnyEventHandler(BuildAnyEventRaised);
            eventSource.CustomEventRaised += new CustomBuildEventHandler(BuildCustomEventRaised);

            // Clear the output
            this.Context.Output.Clear();
        }

        /// <summary>
        /// Releases the resources allocated to the logger at the time of initialization or during the build. This method is called when the logger is unregistered from the engine, after all events are raised. A host of MSBuild typically unregisters loggers immediately before quitting.
        /// </summary>
        public void Shutdown() {
        }

        /// <summary>
        /// Shoulds the log message.
        /// </summary>
        /// <param name="importance">The importance.</param>
        /// <returns></returns>
        private bool ShouldLogMessage(MessageImportance importance) {

            bool result = false;

            if (importance == MessageImportance.High && this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Minimal)) {
                result = true;
            } else if (importance == MessageImportance.Normal && this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Normal)) {
                result = true;
            } else if (importance == MessageImportance.Low && this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Detailed)) {
                result = true;
            }

            return result;
        }

        /// <summary>
        /// Determines whether [is verbosity at least] [the specified verbosity].
        /// </summary>
        /// <param name="verbosity">The verbosity.</param>
        /// <returns>
        /// 	<c>true</c> if [is verbosity at least] [the specified verbosity]; otherwise, <c>false</c>.
        /// </returns>
        private bool IsVerbosityAtLeast(BuildingLoggerVerbosity verbosity) {
            return this.Context.Verbosity >= verbosity;
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="eventArgs">The <see cref="Microsoft.Build.Framework.BuildEventArgs"/> instance containing the event data.</param>
        private void LogEvent(BuildEventArgs eventArgs) {
            this.LogMessage(string.Format(CultureInfo.InvariantCulture, "{0}", eventArgs.Message));
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="eventArgs">The <see cref="Microsoft.Build.Framework.BuildStatusEventArgs"/> instance containing the event data.</param>
        private void LogEvent(BuildStatusEventArgs eventArgs) {
            this.LogMessage(string.Format(CultureInfo.InvariantCulture, "{0}", eventArgs.Message));
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="eventArgs">The <see cref="Microsoft.Build.Framework.BuildMessageEventArgs"/> instance containing the event data.</param>
        private void LogEvent(BuildMessageEventArgs eventArgs) {
            this.LogMessage(string.Format(CultureInfo.InvariantCulture, "{0}", eventArgs.Message));
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="eventArgs">The <see cref="Microsoft.Build.Framework.BuildWarningEventArgs"/> instance containing the event data.</param>
        private void LogEvent(BuildWarningEventArgs eventArgs) {
            this.LogMessage(string.Format(CultureInfo.InvariantCulture, "Warning ({0}): {1} (File {2}, Line {3}, Column {4})", eventArgs.Code, eventArgs.Message, eventArgs.File, eventArgs.LineNumber, eventArgs.ColumnNumber));
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        private void LogEvent(BuildErrorEventArgs eventArgs) {
            this.LogMessage(string.Format(CultureInfo.InvariantCulture, "ERROR ({0}): {1} (File {2}, Line {3}, Column {4})", eventArgs.Code, eventArgs.Message, eventArgs.File, eventArgs.LineNumber, eventArgs.ColumnNumber));
        }

        /// <summary>
        /// Logs the message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void LogMessage(string message) {

            // Indent Message
            StringBuilder messageLine = new StringBuilder();
            for (int counter = indent; counter > 0; counter--) {
                messageLine.Append("\t");
            }

            // Write the message to the output
            messageLine.Append(message);
            this.Context.Output.WriteLine(messageLine.ToString());
        }

        /// <summary>
        /// Logs the empty message.
        /// </summary>
        private void LogEmptyMessage() {
            // Write the message to the output
            this.Context.Output.WriteLine(string.Empty);
        }

        /// <summary>
        /// Builds the error raised.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.BuildErrorEventArgs"/> instance containing the event data.</param>
        private void BuildErrorRaised(object sender, BuildErrorEventArgs e) {
            this.LogEvent(e);
        }

        /// <summary>
        /// Builds the warning raised.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.BuildWarningEventArgs"/> instance containing the event data.</param>
        private void BuildWarningRaised(object sender, BuildWarningEventArgs e) {
            this.LogEvent(e);
        }

        /// <summary>
        /// Builds the message raised.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.BuildMessageEventArgs"/> instance containing the event data.</param>
        private void BuildMessageRaised(object sender, BuildMessageEventArgs e) {
            if (this.ShouldLogMessage(e.Importance) == true) {
                this.LogEvent(e);
            }
        }

        /// <summary>
        /// Builds the build started.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.BuildStartedEventArgs"/> instance containing the event data.</param>
        private void BuildBuildStarted(object sender, BuildStartedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Quiet)) {
                this.LogEvent(e);
                this.LogEmptyMessage();
            }
        }

        /// <summary>
        /// Builds the build finished.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.BuildFinishedEventArgs"/> instance containing the event data.</param>
        private void BuildBuildFinished(object sender, BuildFinishedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Quiet)) {
                this.LogEmptyMessage();
                this.LogEvent(e);
            }
        }

        /// <summary>
        /// Builds the project started.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.ProjectStartedEventArgs"/> instance containing the event data.</param>
        private void BuildProjectStarted(object sender, ProjectStartedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Minimal)) {
                this.LogEvent(e);
                indent++;
            }
        }

        /// <summary>
        /// Builds the project finished.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.ProjectFinishedEventArgs"/> instance containing the event data.</param>
        private void BuildProjectFinished(object sender, ProjectFinishedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Minimal)) {
                indent--;
                this.LogEvent(e);
            }
        }

        /// <summary>
        /// Builds the target started.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.TargetStartedEventArgs"/> instance containing the event data.</param>
        private void BuildTargetStarted(object sender, TargetStartedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Normal)) {
                this.LogEvent(e);
                indent++;
            }
        }

        /// <summary>
        /// Builds the target finished.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.TargetFinishedEventArgs"/> instance containing the event data.</param>
        private void BuildTargetFinished(object sender, TargetFinishedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Normal)) {
                indent--;
                this.LogEvent(e);
            }
        }

        /// <summary>
        /// Builds the task started.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.TaskStartedEventArgs"/> instance containing the event data.</param>
        private void BuildTaskStarted(object sender, TaskStartedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Detailed)) {
                this.LogEvent(e);
                indent++;
            }
        }

        /// <summary>
        /// Builds the task finished.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.TaskFinishedEventArgs"/> instance containing the event data.</param>
        private void BuildTaskFinished(object sender, TaskFinishedEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Detailed)) {
                indent--; 
                this.LogEvent(e);                
            }
        }

        /// <summary>
        /// Builds the status event raised.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.BuildStatusEventArgs"/> instance containing the event data.</param>
        private void BuildStatusEventRaised(object sender, BuildStatusEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Diagnostic)) {
                this.LogEvent(e);
            }
        }

        /// <summary>
        /// Builds the custom event raised.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.CustomBuildEventArgs"/> instance containing the event data.</param>
        private void BuildCustomEventRaised(object sender, CustomBuildEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Diagnostic)) {
                this.LogEvent(e);
            }
        }

        /// <summary>
        /// Builds any event raised.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="Microsoft.Build.Framework.BuildEventArgs"/> instance containing the event data.</param>
        private void BuildAnyEventRaised(object sender, BuildEventArgs e) {
            if (this.IsVerbosityAtLeast(BuildingLoggerVerbosity.Diagnostic)) {
                this.LogEvent(e);
            }
        }
    }
}
